home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / sun / volume1 / cknfs < prev    next >
Encoding:
Text File  |  1989-06-21  |  14.7 KB  |  617 lines

  1. Path: uunet!cs.utexas.edu!rutgers!aramis.rutgers.edu!dartagnan.rutgers.edu!mcgrew
  2. From: mcgrew@dartagnan.rutgers.edu (Charles Mcgrew)
  3. Newsgroups: comp.sources.sun
  4. Subject: v01i033:  Cknfs - Test NFS paths for validity
  5. Message-ID: <Jun.21.13.31.16.1989.29033@dartagnan.rutgers.edu>
  6. Date: 21 Jun 89 17:31:18 GMT
  7. Organization: Rutgers Univ., New Brunswick, N.J.
  8. Lines: 606
  9. Approved: mcgrew@aramis.rutgers.edu
  10.  
  11. Submitted-by: aklietz@ncsa.uiuc.edu
  12. Posting-number: Volume 1, Issue 33
  13. Archive-name: cknfs
  14.  
  15. #! /bin/sh
  16. # This is a shell archive.  Remove anything before this line, then unpack
  17. # it by saving it into a file and typing "sh file".  To overwrite existing
  18. # files, type "sh file -c".  You can also feed this as standard input via
  19. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  20. # will see the following message at the end:
  21. #        "End of shell archive."
  22. # Contents:  cknfs.c
  23. # Wrapped by aklietz@occam on Wed May 31 18:25:22 1989
  24. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  25. if test -f 'cknfs.c' -a "${1}" != "-c" ; then 
  26.   echo shar: Will not clobber existing file \"'cknfs.c'\"
  27. else
  28. echo shar: Extracting \"'cknfs.c'\" \(13080 characters\)
  29. sed "s/^X//" >'cknfs.c' <<'END_OF_FILE'
  30. X/*
  31. X * cknfs - Check for dead NFS servers
  32. X *
  33. X * Don't you hate it when you login to an NFS client, only to find
  34. X * yourself hung because one of your paths points to a dead NFS server?
  35. X * Well, this program fixes that problem.  It takes a list of execution
  36. X * paths as arguments.   Each path is examined for an NFS mount point.
  37. X * If found, the corresponding NFS server is checked.   Paths that lead
  38. X * to dead NFS servers are ignored.  The remaining paths are printed to
  39. X * stdout.  No more hung logins!
  40. X *
  41. X * Usage: cknfs -e -s -t# -v -D -L paths
  42. X *  
  43. X *       -e     silent, do not print paths
  44. X *       -s     print paths in sh format (colons)
  45. X *       -t n   timeout interval before assuming an NFS
  46. X *              server is dead (default 10 seconds)
  47. X *       -v     verbose
  48. X *       -D     debug
  49. X *       -L     expand symbolic links
  50. X *
  51. X * Typical examples:
  52. X *
  53. X *    set path = `cknfs /bin /usr/bin /usr/ucb . /usr6/bin /sdg/bin`
  54. X *    alias cd 'cknfs -e \!*; if ($status == 0) chdir \!*'
  55. X *
  56. X * The latter example prevents you from hanging if you cd to a
  57. X * directory that leads to a dead NFS server.
  58. X *
  59. X * Adminstrative note:  You can still get hung if your administator 
  60. X * mixes NFS mount points from different machines in the same parent
  61. X * directory or if your administrator mixes regular directories and
  62. X * NFS mount points in the same parent directory.
  63. X *
  64. X * The best organization is an overall /nfs directory with subdirectories 
  65. X * for each machine.   For example, if you have 3 NFS servers named
  66. X * "newton", "bardeen", and "zaphod", a good organization would be
  67. X *
  68. X *    /nfs/bardeen/apps
  69. X *    /nfs/bardeen/bin
  70. X *    /nfs/newton/bin
  71. X *    /nfs/newton/local
  72. X *    /nfs/zaphod/bin
  73. X *    /nfs/zaphod/sdg
  74. X *
  75. X * NEVER MIX MOUNT POINTS FROM DIFFERENT MACHINES IN THE SAME
  76. X * PARENT DIRECTORY!  Follow this rule and use this program and
  77. X * you will never get hung by NFS again.
  78. X */
  79. X
  80. X/*
  81. X * Copyright (c) 1988, The Board of Trustees of the University of Illinois
  82. X * National Center for Supercomputing Applications.
  83. X *
  84. X * No warranty is expressed or implied.
  85. X * Unlimited redistribution permitted.
  86. X *
  87. X */
  88. X
  89. Xstatic char *RCSid = "$Header: cknfs.c,v 1.4 89/05/31 18:24:49 aklietz Exp $";
  90. X
  91. X/*
  92. X * $Log:    cknfs.c,v $
  93. X * Revision 1.4  89/05/31  18:24:49  aklietz
  94. X * Fix bug introduced in rev 1.3 that did hangable lstat before
  95. X * checking for NFS mount point.
  96. X * Add support for Ultrix.
  97. X * 
  98. X * Revision 1.3  89/05/29  03:30:55  aklietz
  99. X * Terminate silently if no args in -e mode.
  100. X * Fix omission of chdir("/") during parse of symlink to absolute path.
  101. X * 
  102. X * Revision 1.2  89/05/26  14:14:35  aklietz
  103. X * Baseline for release
  104. X * 
  105. X * Revision 1.1  89/05/26  13:37:39  aklietz
  106. X * Initial revision
  107. X * 
  108. X */
  109. X
  110. X#include <sys/param.h>
  111. X#include <errno.h>
  112. X#include <sys/types.h>
  113. X#include <sys/stat.h>
  114. X#include <signal.h>
  115. X#include <ctype.h>
  116. X#include <stdio.h>
  117. X#include <sys/time.h>
  118. X#include <sys/socket.h>
  119. X#include <arpa/inet.h>
  120. X#include <netdb.h>
  121. X#include <rpc/rpc.h>
  122. X#include <rpc/pmap_prot.h>
  123. X#include <rpc/pmap_clnt.h>
  124. X#include <nfs/nfs.h>
  125. X
  126. X/*
  127. X * Make initial program
  128. X * May 1989, Alan Klietz (aklietz@ncsa.uiuc.edu)
  129. X */
  130. X
  131. X#define DEFAULT_TIMEOUT 10  /* Default timeout for checking NFS server */
  132. X
  133. Xextern char *realloc();
  134. Xextern char *strchr(), *strrchr(), *strtok();
  135. X
  136. Xstruct m_mlist {
  137. X    int mlist_checked; /* -1 if bad, 0 if not checked, 1 if ok */
  138. X    struct m_mlist *mlist_next;
  139. X    char *mlist_dir;
  140. X    char *mlist_fsname;
  141. X    int mlist_isnfs;
  142. X};
  143. Xstatic struct m_mlist *firstmnt;
  144. X
  145. Xstatic int errflg;
  146. Xstatic int eflg, sflg, vflg, Dflg, Lflg;
  147. Xstatic int timeout = DEFAULT_TIMEOUT;
  148. Xstatic char prefix[MAXPATHLEN];
  149. Xstruct m_mlist *isnfsmnt();
  150. Xchar *xalloc();
  151. Xvoid mkm_mlist();
  152. X
  153. Xint
  154. Xmain(argc, argv)
  155. Xint argc;
  156. Xchar **argv;
  157. X{
  158. X    register int n;
  159. X    register char *s;
  160. X    int good = 0;
  161. X    char outbuf[BUFSIZ];
  162. X    char errbuf[BUFSIZ];
  163. X    extern int optind;
  164. X    extern char *optarg;
  165. X
  166. X
  167. X    /*
  168. X     * Avoid intermixing stdout and stderr
  169. X     */
  170. X    setvbuf(stdout, outbuf, _IOFBF, sizeof(outbuf));
  171. X    setvbuf(stderr, errbuf, _IOLBF, sizeof(errbuf));
  172. X
  173. X    while ((n = getopt(argc, argv, "est:vDL")) != EOF)
  174. X        switch(n) {
  175. X            case 'e':    ++eflg;
  176. X                    break;
  177. X
  178. X            case 's':    ++sflg;
  179. X                    break;
  180. X
  181. X            case 't':    timeout = atoi(optarg);
  182. X                    break;
  183. X
  184. X            case 'v':    ++vflg;
  185. X                    break;
  186. X
  187. X            case 'D':    ++Dflg; ++vflg;
  188. X                    break;
  189. X
  190. X            case 'L':    ++Lflg;
  191. X                    break;
  192. X
  193. X            default:
  194. X                    ++errflg;
  195. X        }
  196. X
  197. X    if (argc <= optind && !eflg) /* no paths */
  198. X        ++errflg;
  199. X
  200. X    if (errflg) {
  201. X        fprintf(stderr, "Usage: %s -e -s -t# -v -D -L paths\n", argv[0]);
  202. X        fprintf(stderr, "\tCheck paths for dead NFS servers\n");
  203. X        fprintf(stderr, "\tGood paths are printed to stdout\n\n");
  204. X        fprintf(stderr, "\t -e\tsilent, do not print paths\n");
  205. X        fprintf(stderr, "\t -s\tprint paths in sh format (semicolons)\n");
  206. X        fprintf(stderr, "\t -t n\ttimeout interval before assuming an NFS\n");
  207. X        fprintf(stderr, "\t\tserver is dead (default 10 seconds)\n");
  208. X        fprintf(stderr, "\t -v\tverbose\n");
  209. X        fprintf(stderr, "\t -D\tdebug\n");
  210. X        fprintf(stderr, "\t -L\texpand symbolic links\n\n");
  211. X        exit(1);
  212. X    }
  213. X
  214. X    for (n = optind; n < argc; ++n) {
  215. X        s = argv[n];
  216. X        if (Dflg)
  217. X            fprintf(stderr, "chkpath(%s)\n", s);
  218. X        if (chkpath(s)) {
  219. X            if (good++ && !eflg)
  220. X                putchar(sflg ? ':' : ' ');
  221. X            if (!eflg)
  222. X                fputs(Lflg ? prefix : s, stdout);
  223. X        } else
  224. X            if (vflg)
  225. X                fprintf(stderr, "path skipped: %s\n",
  226. X                    Lflg ? prefix : s);
  227. X    }
  228. X
  229. X    if (good && !eflg)
  230. X        putchar('\n');
  231. X
  232. X    fflush(stderr);
  233. X    fflush(stdout);
  234. X
  235. X    exit(good == 0 && optind < argc );
  236. X}
  237. X
  238. X
  239. Xint chkpath(path)
  240. X/*
  241. X * Check path for accessibility.  Return 1 if ok, 0 if error
  242. X */
  243. Xchar *path;
  244. X{
  245. X    if (*path != '/') { /* If not absolute path, get initial prefix */
  246. X        if (getwd(prefix) < 0) {
  247. X            fprintf(stderr, "%s\n", prefix);
  248. X            return 0;
  249. X        }
  250. X    }
  251. X    return(_chkpath(path));
  252. X}
  253. X
  254. X
  255. X#define NTERMS 256
  256. X
  257. Xint
  258. X_chkpath(path)
  259. Xchar *path;
  260. X{
  261. X    register char *s, *s2;
  262. X    register int i, front=0, back=0;
  263. X    struct stat stb;
  264. X    struct m_mlist *mlist;
  265. X    char p[MAXPATHLEN];
  266. X    char symlink[MAXPATHLEN];
  267. X    char *queue[NTERMS];
  268. X
  269. X
  270. X    /*
  271. X     * Copy path to working storage
  272. X     */
  273. X    strncpy(p, path, sizeof(p)-1);
  274. X
  275. X    if (*p == '/') { /* If absolute path, start at root */
  276. X        *prefix = '\0';
  277. X        (void) chdir("/");
  278. X    }
  279. X
  280. X    if (Dflg)
  281. X        fprintf(stderr, "_chkpath(%s) prefix=%s\n", path, prefix);
  282. X
  283. X    /*
  284. X     * Put directory terms on FIFO queue
  285. X     */
  286. X    for (s = strtok(p, "/"); s != NULL; s = strtok(NULL, "/")) {
  287. X        if (back >= NTERMS) {
  288. X            fprintf(stderr, "Too many subdirs: %s\n", path);
  289. X            goto fail;
  290. X        }
  291. X        queue[back++] = s;
  292. X    }
  293. X    /*  queue[front] = a, queue[front+1] = b, ... queue[back] = null */
  294. X
  295. X    /*
  296. X     * Scan queue of directory terms, expanding 
  297. X     * symbolic links recursively.
  298. X     */
  299. X    while (front != back) {
  300. X        s = queue[front++];
  301. X        /* Dot */
  302. X        if (s[0] == '.' && s[1] == '\0')
  303. X            continue;
  304. X        /* Dot Dot */
  305. X        if (s[0] == '.' && s[1] == '.' && s[2] == '\0') {
  306. X            if (chdir("..") < 0) {
  307. X                perror("chdir(..)");
  308. X                goto fail;
  309. X            }
  310. X            /* Remove trailing component of prefix */
  311. X            if ((s2 = strrchr(prefix, '/')) != NULL)
  312. X                *s2 = '\0';
  313. X            continue;
  314. X        } else {
  315. X            strcat(prefix, "/");
  316. X            strcat(prefix, s);
  317. X        }
  318. X
  319. X        if ((mlist = isnfsmnt(prefix)) != NULL) /* NFS mount? */
  320. X            if (chknfsmnt(mlist) <= 0)
  321. X                return 0;
  322. X
  323. X        /* Check if symlink */
  324. X        if (lstat(s, &stb) < 0) {
  325. X            perror(s);
  326. X            goto fail;
  327. X        }
  328. X        if ((stb.st_mode & S_IFMT) != S_IFLNK) {
  329. X            /* not symlink */
  330. X            if (chdir(s) < 0) {
  331. X                fprintf(stderr, "chdir(%s): ", s);
  332. X                perror("");
  333. X                goto fail;
  334. X            }
  335. X            continue;
  336. X        }
  337. X
  338. X        /* Remove symlink from tail of prefix */
  339. X        if ((s2 = strrchr(prefix, '/')) != NULL)
  340. X            *s2 = '\0';
  341. X        /* 
  342. X         * Read symlink
  343. X         */
  344. X        if ((i = readlink(s, symlink, MAXPATHLEN-1)) < 0) {
  345. X            perror(s);
  346. X            goto fail;
  347. X        }
  348. X        symlink[i] = '\0'; /* null terminate */
  349. X
  350. X        /*
  351. X         * Recursively check symlink
  352. X         */
  353. X        if (_chkpath(symlink) == 0)
  354. X            return 0;
  355. X    }
  356. X
  357. X    return 1;
  358. X
  359. Xfail:
  360. X    return 0;
  361. X}
  362. X    
  363. X
  364. Xstruct m_mlist *
  365. Xisnfsmnt(path)
  366. X/*
  367. X * Return 1 if path is NFS mount point
  368. X */
  369. Xchar *path;
  370. X{
  371. X    register struct m_mlist *mlist;
  372. X    static int init;
  373. X
  374. X    if (init == 0) {
  375. X        ++init;
  376. X        mkm_mlist();
  377. X    }
  378. X
  379. X    for (mlist = firstmnt; mlist != NULL; mlist = mlist->mlist_next) {
  380. X        if (mlist->mlist_isnfs == 0)
  381. X            continue;
  382. X        if (strcmp(mlist->mlist_dir, path) == 0)
  383. X            return(mlist);
  384. X    }
  385. X    return NULL;
  386. X}
  387. X
  388. X
  389. Xstatic int
  390. Xget_inaddr(saddr, host)
  391. X/*
  392. X * Translate host name to Internet address.
  393. X * Return 1 if ok, 0 if error
  394. X */
  395. Xstruct sockaddr_in *saddr;
  396. Xchar *host;
  397. X{
  398. X    register struct hostent *hp;
  399. X
  400. X    memset((char *)saddr, 0, sizeof(struct sockaddr_in));
  401. X    saddr->sin_family = AF_INET;
  402. X    if ((saddr->sin_addr.s_addr = inet_addr(host)) == -1) {
  403. X        if ((hp = gethostbyname(host)) == NULL) {
  404. X            fprintf(stderr, "%s: unknown host\n", host);
  405. X            return 0;
  406. X        }
  407. X        memcpy((char *)&saddr->sin_addr, hp->h_addr, hp->h_length);
  408. X    }
  409. X    return 1;
  410. X}
  411. X
  412. X
  413. Xint
  414. Xchknfsmnt(mlist)
  415. X/*
  416. X * Ping the NFS server indicated by the given mnt entry
  417. X */
  418. Xregister struct m_mlist *mlist;
  419. X{
  420. X    register char *s;
  421. X    register struct m_mlist *mlist2;
  422. X    CLIENT *client;
  423. X    struct sockaddr_in saddr;
  424. X    int sock, len;
  425. X    struct timeval tottimeout;
  426. X    struct timeval interval;
  427. X    int prognum, vers, port;
  428. X    struct pmap pmap;
  429. X    enum clnt_stat rpc_stat;
  430. X    static char p[MAXPATHLEN];
  431. X
  432. X    if (Dflg)
  433. X        fprintf(stderr, "chknfsmnt(%s)\n", mlist->mlist_fsname);
  434. X
  435. X    if (mlist->mlist_checked) /* if already checked this mount point */
  436. X        return (mlist->mlist_checked);
  437. X
  438. X    /*
  439. X     * Save path to working storage and strip colon
  440. X     */
  441. X    strncpy(p, mlist->mlist_fsname, sizeof(p)-1);
  442. X    if ((s = strchr(p, ':')) != NULL)
  443. X        *s = '\0';
  444. X    len = strlen(p);
  445. X
  446. X    /*
  447. X     * See if remote host already checked via another mount point
  448. X     */
  449. X    for (mlist2 = firstmnt; mlist2 != NULL; mlist2 = mlist2->mlist_next)
  450. X        if (strncmp(mlist2->mlist_fsname, p, len) == 0 
  451. X                && mlist2->mlist_checked)
  452. X            return(mlist2->mlist_checked);
  453. X
  454. X    mlist->mlist_checked = -1; /* set failed */
  455. X    if (vflg)
  456. X        fprintf(stderr, "Checking %s..\n", p);
  457. X    interval.tv_sec = 2;  /* retry interval */
  458. X    interval.tv_usec = 0;
  459. X
  460. X    /*
  461. X     * Parse internet address
  462. X     */
  463. X    if (get_inaddr(&saddr, p) == 0)
  464. X        return 0;
  465. X    /*
  466. X     * Get socket to remote portmapper
  467. X     */
  468. X    saddr.sin_port = htons(PMAPPORT);
  469. X    sock = RPC_ANYSOCK;
  470. X    if ((client = clntudp_create(&saddr, PMAPPROG, PMAPVERS, interval, 
  471. X            &sock)) == NULL) {
  472. X        clnt_pcreateerror(p);
  473. X        return 0;
  474. X    }
  475. X    /*
  476. X     * Query portmapper for port # of NFS server
  477. X     */
  478. X    pmap.pm_prog = NFS_PROGRAM;
  479. X    pmap.pm_vers = NFS_VERSION;
  480. X    pmap.pm_prot = IPPROTO_UDP;
  481. X    pmap.pm_port = 0;
  482. X    tottimeout.tv_sec = timeout;  /* total timeout */
  483. X    tottimeout.tv_usec = 0;
  484. X    if ((rpc_stat = clnt_call(client, PMAPPROC_GETPORT, xdr_pmap, &pmap,
  485. X            xdr_u_int, &port, tottimeout)) != RPC_SUCCESS) {
  486. X        clnt_perror(client, p);
  487. X        clnt_destroy(client);
  488. X        return 0;
  489. X    }
  490. X    clnt_destroy(client);
  491. X
  492. X    if (port == 0) {
  493. X        fprintf(stderr, "%s: NFS server not registered\n", p);
  494. X        return 0;
  495. X    }
  496. X    /*
  497. X     * Get socket to NFS server
  498. X     */
  499. X    saddr.sin_port = htons(port);
  500. X    sock = RPC_ANYSOCK;
  501. X    if ((client = clntudp_create(&saddr, NFS_PROGRAM, NFS_VERSION,
  502. X            interval, &sock)) == NULL) {
  503. X        clnt_pcreateerror(p);
  504. X        return 0;
  505. X    }
  506. X    /*
  507. X     * Ping NFS server
  508. X     */
  509. X    tottimeout.tv_sec = timeout;
  510. X    tottimeout.tv_usec = 0;
  511. X    if ((rpc_stat = clnt_call(client, NULLPROC, xdr_void, (char *)NULL,
  512. X            xdr_void, (char *)NULL, tottimeout)) != RPC_SUCCESS) {
  513. X        clnt_perror(client, p);
  514. X        clnt_destroy(client);
  515. X        return 0;
  516. X    }
  517. X    clnt_destroy(client);
  518. X    mlist->mlist_checked = 1; /* set success */
  519. X    if (vflg)
  520. X        fprintf(stderr, "%s ok\n", p);
  521. X    return 1;
  522. X}
  523. X
  524. X
  525. Xchar *
  526. Xxalloc(size)
  527. X/*
  528. X * Alloc memory with error checks
  529. X */
  530. Xunsigned int size;
  531. X{
  532. X    register char *mem;
  533. X    char *malloc();
  534. X    
  535. X    if ((mem = (char *)malloc(size)) == NULL) {
  536. X        (void) fprintf(stderr, "out of memory\n");
  537. X        exit(1);
  538. X    }
  539. X    return(mem);
  540. X}
  541. X
  542. X/*
  543. X * Begin machine dependent code for mount table 
  544. X */
  545. X
  546. X#if defined(sun)
  547. X#include <mntent.h>
  548. Xvoid
  549. Xmkm_mlist()
  550. X/*
  551. X * Build list of mnt entries - Sun version
  552. X */
  553. X{
  554. X    FILE *mounted;
  555. X    struct m_mlist *mlist;
  556. X    struct mntent *mnt;
  557. X
  558. X    if ((mounted = setmntent(MOUNTED, "r"))== NULL) {
  559. X        perror(MOUNTED);
  560. X        exit(1);
  561. X    }
  562. X    while ((mnt = getmntent(mounted)) != NULL) {
  563. X        mlist = (struct m_mlist *)xalloc(sizeof(*mlist));
  564. X        mlist->mlist_next = firstmnt;
  565. X        mlist->mlist_checked = 0;
  566. X        mlist->mlist_dir = xalloc(strlen(mnt->mnt_dir)+1);
  567. X        (void) strcpy(mlist->mlist_dir, mnt->mnt_dir);
  568. X        mlist->mlist_fsname = xalloc(strlen(mnt->mnt_fsname)+1);
  569. X        (void) strcpy(mlist->mlist_fsname, mnt->mnt_fsname);
  570. X        mlist->mlist_isnfs = !strcmp(mnt->mnt_type, MNTTYPE_NFS);
  571. X        firstmnt = mlist;
  572. X    }
  573. X    (void) endmntent(mounted);
  574. X}
  575. X#endif
  576. X
  577. X#if defined(ultrix)
  578. X#include <sys/fs_types.h>
  579. X#include <sys/mount.h>
  580. Xvoid
  581. Xmkm_mlist()
  582. X/*
  583. X * Build list of mnt entries - Ultrix version (Generic File System)
  584. X */
  585. X{
  586. X    struct m_mlist *mlist;
  587. X    struct fs_data fs_data;
  588. X    int start=0, len;
  589. X
  590. X    while ((len = getmnt(&start, &fs_data, sizeof(fs_data), 
  591. X            NOSTAT_MANY, NULL)) > 0) {
  592. X        mlist = (struct m_mlist *)xalloc(sizeof(*mlist));
  593. X        mlist->mlist_next = firstmnt;
  594. X        mlist->mlist_checked = 0;
  595. X        mlist->mlist_dir = xalloc(strlen(fs_data.fd_path)+1);
  596. X        (void) strcpy(mlist->mlist_dir, fs_data.fd_path);
  597. X        mlist->mlist_fsname = 
  598. X            xalloc(strlen(fs_data.fd_devname)+1);
  599. X        (void) strcpy(mlist->mlist_fsname, fs_data.fd_devname);
  600. X        mlist->mlist_isnfs = (fs_data.fd_fstype == GT_NFS);
  601. X        firstmnt = mlist;
  602. X    }
  603. X    if (len < 0) {
  604. X        perror("getmnt");
  605. X        exit(1);
  606. X    }
  607. X}
  608. X#endif
  609. END_OF_FILE
  610. if test 13093 -ne `wc -c <'cknfs.c'`; then
  611.     echo shar: \"'cknfs.c'\" unpacked with wrong size!
  612. fi
  613. # end of 'cknfs.c'
  614. fi
  615. echo shar: End of shell archive.
  616. exit 0
  617.